
#include "Main.h"
#include "AudioHelper.h"

#include <alsa/asoundlib.h>


/* Handle for the PCM device */
snd_pcm_t* pxPCM_Handle;

/* Playback stream */
snd_pcm_stream_t xCapture_Stream = SND_PCM_STREAM_CAPTURE;

/* This structure contains information about    */
/* the hardware and can be used to specify the  */
/* configuration to be used for the PCM stream. */
snd_pcm_hw_params_t* pxHwParams;

#define AUDIO_BUFFER_SIZE 1024
#define AUDIO_CHANNEL_NUM 2
#define AUDIO_PREPARE_LIMIT 10


/*
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);

static IMMDevice* g_pxDevice = 0;
static IAudioClient* g_pxAudioClient = 0;
static IAudioCaptureClient* g_pxCaptureClient = 0;
*/
static char* g_pcSignalBuffer = 0;
static u_int g_uBlockAlign = 0;
static u_int g_uChannels = 0;
static u_int g_uBufferWritePos = 0;

//REFERENCE_TIME s_uDefaultDevicePeriod;

void AudioHelper::Platform_Initialise()
{
	g_pcSignalBuffer = new char[64*AUDIO_BUFFER_SIZE];
	/* Name of the PCM device, like plughw:0,0          */
	/* The first number is the number of the soundcard, */
	/* the second number is the number of the device.   */
	char* pcPCM_Name;
	
	/* Init pcPCM_Name. */
	pcPCM_Name = strdup("plughw:0,0,0");
	
	/* Allocate the snd_pcm_hw_params_t structure on the stack. */
	snd_pcm_hw_params_alloca(&pxHwParams);
	
	/* Open PCM. The last parameter of this function is the mode. */
	/* If this is set to 0, the standard mode is used. Possible   */
	/* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */
	FridgeLog("Audio ALSA: Opening PCM device.\n");
	if (snd_pcm_open(&pxPCM_Handle, pcPCM_Name, xCapture_Stream, SND_PCM_NONBLOCK) < 0)
	{
		FridgeLogf("Audio ALSA: Error opening PCM device %s\n", pcPCM_Name);
		FAssert(false);
    }
	
	/* Init pxHwParams with full soundcard configuration */
	if (snd_pcm_hw_params_any(pxPCM_Handle, pxHwParams) < 0)
	{
		FridgeLog("Can not configure this PCM device.\n");
		FAssert(false);
	}
	
	u_int uRate = 44100;	/* Sample rate */
	u_int uExact_Rate;	/* Sample rate returned by snd_pcm_hw_params_set_rate_near */
	int iPeriods = 2;	/* Number of periods */
	snd_pcm_uframes_t iPeriodsize = 8192; /* Periodsize (bytes) */
	
	/* Set access type. This can be either    */
	/* SND_PCM_ACCESS_RW_INTERLEAVED or       */
	/* SND_PCM_ACCESS_RW_NONINTERLEAVED.      */
	if (snd_pcm_hw_params_set_access(pxPCM_Handle, pxHwParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
	{
		FridgeLog("Audio ALSA: Error setting access.\n");
		FAssert(false);
	}
	
	/* Set sample format */
	FridgeLog("Audio ALSA: Setting format\n");
	if (snd_pcm_hw_params_set_format(pxPCM_Handle, pxHwParams, SND_PCM_FORMAT_S16_LE) < 0)
	{
		FridgeLog("Audio ALSA: Error setting format.\n");
		FAssert(false);
	}
	
	/* Set sample rate. If the exact rate is not supported */
	/* by the hardware, use nearest possible rate.         */
	uExact_Rate = uRate;
	if (snd_pcm_hw_params_set_rate_near(pxPCM_Handle, pxHwParams, &uExact_Rate, 0) < 0)
	{
		FridgeLog("Audio ALSA: Error setting rate.\n");
		FAssert(false);
	}
	if (uRate != uExact_Rate)
	{
		FridgeLogf("The samplerate %d Hz is not supported by your hardware.\nUsing %d Hz instead.\n", uRate, uExact_Rate);
	}
	
	/* Set number of channels */
	if (snd_pcm_hw_params_set_channels(pxPCM_Handle, pxHwParams, AUDIO_CHANNEL_NUM) < 0)
	{
		FridgeLog("Audio ALSA: Error setting channels.\n");
		FAssert(false);
	}
	
	/* Set number of periods. Periods used to be called fragments. */
	if (snd_pcm_hw_params_set_periods(pxPCM_Handle, pxHwParams, iPeriods, 0) < 0)
	{
		FridgeLog("Audio ALSA: Error setting periods.\n");
		FAssert(false);
	}
	
	/* Set buffer size (in frames). The resulting latency is given by */
	/* latency = iPeriodsize * iPeriods / (uRate * bytes_per_frame)     */
	FridgeLog("Audio ALSA: Setting buffer size\n");
	if (snd_pcm_hw_params_set_buffer_size(pxPCM_Handle, pxHwParams, (iPeriodsize * iPeriods)>>2) < 0)
	{
		FridgeLog("Audio ALSA: Error setting buffersize.\n");
		FAssert(false);
	}
	
	//If your hardware does not support a buffersize of 2^n, you can use the function snd_pcm_hw_params_set_buffer_size_near. This works similar to snd_pcm_hw_params_set_rate_near. Now we apply the configuration to the PCM device pointed to by pxPCM_Handle. This will also prepare the PCM device.
	
	/* Apply HW parameter settings to */
	/* PCM device and prepare device  */
	FridgeLog("Audio ALSA: Applying interface settings\n");
	if (snd_pcm_hw_params(pxPCM_Handle, pxHwParams) < 0)
	{
		FridgeLog("Audio ALSA: Error setting HW params.\n");
		FAssert(false);
	}
	
	//snd_pcm_hw_params_free(pxHwParams);
}

void AudioHelper::Platform_Update()
{
	if(!pxPCM_Handle)
	{
		return;
	}
	
	int iErr_sndPrep;
	for( u_int uLimitCounter=0; uLimitCounter<AUDIO_PREPARE_LIMIT; uLimitCounter++)
	{
		if( (iErr_sndPrep = snd_pcm_prepare(pxPCM_Handle)) >=0 )
		{
			break;
		}
	}
	
	if( iErr_sndPrep<0 )
	{
		// this happens too often?
		//FridgeLog("Audio ALSA: Could not prepare audio device\n");
	}
	
	snd_pcm_sframes_t lFrames_Ready_Num = uNUM_OSC_SAMPLES;
	
	if(snd_pcm_readi(pxPCM_Handle, g_pcSignalBuffer, uNUM_OSC_SAMPLES)<0)
	{
		// this happens too often?
		//FridgeLog("Audio ALSA: Error reading audio.\n");
	}
	
	char* pcBuffer_Read_Pos = g_pcSignalBuffer;
	for( u_int uFrame = 0; uFrame < uNUM_OSC_SAMPLES; ++uFrame )
	{
		short* psSample = reinterpret_cast< short* >( pcBuffer_Read_Pos );
		s_afLeftSamples[uFrame] = static_cast<float>( *psSample ) / 32768.0f;
		++psSample;
		s_afRightSamples[uFrame] = static_cast<float>( *psSample ) / 32768.0f;
		pcBuffer_Read_Pos+=AUDIO_CHANNEL_NUM*sizeof(short);
	}
	
}

void AudioHelper::Platform_Shutdown()
{
	if(!pxPCM_Handle)
	{
		return;
	}
	
	snd_pcm_close (pxPCM_Handle);
	
	//delete g_pcSignalBuffer;
}

// eof
